using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Security.AccessControl;
using System.Security.Principal;
using System.IO;
using CodePlex.Resourceful;
using CodePlex.Resourceful.Amazon.S3;



namespace CodePlex.SpaceBlock.FS
{
    public class LocalFileSystem : BaseFileSystem, IFileSystem, ISupportsACL, ISupportsMove
    {

        private readonly Tracer _tracer;

        public LocalFileSystem()
        {
            _tracer = new Tracer();
        }
       
        public string Caption
        {
            get 
            {
                return string.Format("Local File System - {0}", this.ShortCaption) ; 
            }
        }

        public string ShortCaption
        {
            get
            {
                return Environment.MachineName;
            }
        }

        public Tracer Tracer
        {
            get
            {
                return _tracer;
            }
        }

        public bool Exists(Path path)
        {
            this.CheckNull(path);
            if (path.Level == 0)
            {
                return true; // root always exists
            }

            string localPath = this.ToLocalPath(path);
            return path.IsFolder ? System.IO.Directory.Exists(localPath) : System.IO.File.Exists(localPath);
        }

        public void EnsureFolderExists(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            if (path.Level == 0)
            {
                return; // root always exists
            }

            string localPath = this.ToLocalPath(path);

            if (path.Level < 2)
            {
                if (System.IO.Directory.Exists(localPath))
                {
                    return;
                }
                throw CodePlexException.Format("Cannot create folder '{0}'.", path);
            }

            System.IO.Directory.CreateDirectory(localPath);

        }



        public void Move(Path source, Path destination)
        {
            // TODO only file sources supported right now

            this.CheckNull(source);
            this.CheckFile(source); 

            this.CheckNull(destination);
            if (destination.IsFolder)
            {
                destination = destination.Combine(source.Name);
            }
            this.CheckFile(destination);


            string localSource = this.ToLocalPath(source);
            string localDest = this.ToLocalPath(destination);


            System.IO.File.Move(localSource, localDest);
        }


        public void Delete(Path path)
        {
            this.CheckNull(path);

            if (path.Level < 2)
            {
                throw CodePlexException.Format("Cannot delete {1} '{0}'.", path, (path.IsFolder ? "folder" : "file"));
            }

            if (this.Exists(path))
            {
                string localPath = this.ToLocalPath(path);

                if (path.IsFolder)
                {
                    System.IO.Directory.Delete(localPath, true);
                }
                else
                {
                    System.IO.File.Delete(localPath);
                }
            }
        }

        public ReadOnlyList<IPathItem> GetItems(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            List<IPathItem> rt = new List<IPathItem>();

            if (path.Level == 0)
            {
                foreach (IFolderItem folder in this.GetFolders(path))
                {
                    rt.Add(folder);
                }
                return new ReadOnlyList<IPathItem>(rt);
            }
            
            foreach (string entry in System.IO.Directory.GetFileSystemEntries(this.ToLocalPath(path)))
            {
                if (System.IO.File.Exists(entry))
                {
                    rt.Add(new FileItem(this,entry));
                }
                if (System.IO.Directory.Exists(entry))
                {
                    rt.Add(new LocalFolderItem(this,entry));
                }
            }

            return new ReadOnlyList<IPathItem>(rt);

        }

        public ReadOnlyList<IFolderItem> GetFolders(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            List<IFolderItem> rt = new List<IFolderItem>();


            if (path.Level == 0)
            {
                foreach (string drive in System.IO.Directory.GetLogicalDrives())
                {
                    rt.Add(new LocalFolderItem(this,drive));
                }
                return new ReadOnlyList<IFolderItem> (rt);
            }

            try
            {
                foreach (string entry in System.IO.Directory.GetDirectories(this.ToLocalPath(path)))
                {
                    rt.Add(new LocalFolderItem(this, entry));
                }
            }
            catch (UnauthorizedAccessException)
            {

            }
            catch (System.IO.IOException)
            {

            }

            return new ReadOnlyList<IFolderItem>(rt);
        }


        public ReadOnlyList<IFileItem> GetFiles(Path path)
        {
            this.CheckNull(path);
            this.CheckFolder(path);

            List<IFileItem> rt = new List<IFileItem>();

            if (path.Level == 0)
            {
                return ReadOnlyList<IFileItem>.Empty;
            }
            try
            {
                string[] files = System.IO.Directory.GetFiles(  this.ToLocalPath(path));

                foreach (string entry in files)
                {
                    rt.Add(new FileItem(this, entry));
                }

                return new ReadOnlyList<IFileItem>(rt);
            }
            catch (Exception ex)
            {
                if (ex is UnauthorizedAccessException || ex.Message == "The device is not ready.\r\n")
                {
                    return new ReadOnlyList<IFileItem>(rt);
                }

                // for mounted shares on os x for example
                if (ex is ArgumentException && ex.Message == "Illegal characters in path.")
                {
                    return new ReadOnlyList<IFileItem>(rt);
                }

                throw;
            }

        }

        public IPathItem GetItem(Path path)
        {
            this.CheckNull(path);

            if (!this.Exists(path))
            {
                return null;
            }
            string localPath = this.ToLocalPath(path);

            if (path.IsFolder)
            {
                return new LocalFolderItem(this, localPath);
            }
            else
            {
                return new FileItem(this, localPath);
            }
           
        }

        public void EnsureFileExists(Path path)
        {
            this.CheckNull(path);
            this.CheckFile(path);

            if (path.Level < 2)
            {
                throw CodePlexException.Format("Cannot create file '{0}'.", path);
            }

            if (!this.Exists(path))
            {
                using (System.IO.FileStream fs = System.IO.File.Create(this.ToLocalPath(path)))
                {

                }
            }
        }

        public Path FromLocalPath(string path)
        {

            bool isFile = File.Exists(path);

            bool isFolder = Directory.Exists(path);


            if (!isFile && !isFolder)
            {
                // guess
                isFile = !path.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString());
                isFolder = path.EndsWith(System.IO.Path.DirectorySeparatorChar.ToString());
            }

            string rt = path.Replace(System.IO.Path.DirectorySeparatorChar, Path.SeparatorChar);
            if (!rt.StartsWith(Path.Separator))
            {
                rt = Path.SeparatorChar + rt;
            }

            if (isFile)
            {
                return Path.Parse(rt);
            }

            if (isFolder)
            {
                if (!rt.EndsWith(Path.Separator))
                {
                    rt += Path.Separator;
                }
                return Path.Parse(rt);
            }

            throw CodePlexException.Format("Invalid path '{0}'.", path);

        }

        public string ToLocalPath(Path path)
        {
            if (System.IO.Path.DirectorySeparatorChar == Path.SeparatorChar)
            {
                return path.ToString();
            }
            else
            {
                return path.ToString().Substring(1).Replace(Path.SeparatorChar, System.IO.Path.DirectorySeparatorChar);
            }
        }

        public void Transfer(IFileItem source, Path destination, Action<long> bytesTransferred)
        {
            this.CheckNull(destination);
            this.CheckFile(destination);

            using (System.IO.Stream stream = source.GetInputStream())
            {
                using (System.IO.Stream output = System.IO.File.Create(this.ToLocalPath(destination)))
                {
                    Utils.BufferedTransfer(stream, output, bytesTransferred);
                }
            }
        }

        public Metadata GetMetadata(Path path)
        {
            Metadata rt = new Metadata();

            string localPath = this.ToLocalPath(path);

            this.AddMetadata(rt, BaseMetadataKeys.Path, path.ToString());

            if (path.Level == 0)
            {
                this.AddMetadata(rt, BaseMetadataKeys.Type, "File System");
            }
            else if (path.Level == 1)
            {
                string cat = MDCategories.LogicalDrive;

                this.AddMetadata(rt, BaseMetadataKeys.Type, cat);
                this.AddMetadataLocalPath(rt, cat, localPath);

                DriveInfo info = new DriveInfo(localPath);
                if (info.IsReady)
                {
                    rt.Set(cat, MDKeys.DriveType, MDDescriptions.DriveType, info.DriveType.ToString());
                    rt.Set(cat, MDKeys.DriveFormat, MDDescriptions.DriveFormat, info.DriveFormat);
                    rt.Set(cat, MDKeys.VolumeLabel, MDDescriptions.DriveVolumeLabel, info.VolumeLabel);
                    rt.Set(cat, MDKeys.TotalSize, MDDescriptions.DriveTotalSize, this.ComputeSizeString(info.TotalSize));
                    rt.Set(cat, MDKeys.TotalFreeSpace, MDDescriptions.DriveTotalFreeSpace, this.ComputeSizeString(info.TotalFreeSpace));
                    rt.Set(cat, MDKeys.AvailableFreeSpace, MDDescriptions.DriveAvailableFreeSpace, this.ComputeSizeString(info.AvailableFreeSpace));
                }
            }
            else if (path.IsFolder)
            {
                string cat = MDCategories.Folder;

                this.AddMetadata(rt, BaseMetadataKeys.Type, cat);
                this.AddMetadataLocalPath(rt, cat, localPath);

                DirectoryInfo info = new DirectoryInfo(localPath);
                rt.Set(cat, MDKeys.Attributes, MDDescriptions.FolderAttributes, info.Attributes.ToString());
                rt.Set(cat, MDKeys.CreationTime, MDDescriptions.FolderCreationTime, this.ComputeDateTimeString(info.CreationTime));
                rt.Set(cat, MDKeys.LastAccessTime, MDDescriptions.FolderLastAccessTime, this.ComputeDateTimeString(info.LastAccessTime));
                rt.Set(cat, MDKeys.LastWriteTime, MDDescriptions.FolderLastWriteTime, this.ComputeDateTimeString(info.LastWriteTime));
            }
            else
            {
                string cat = MDCategories.File;

                this.AddMetadata(rt, BaseMetadataKeys.Type, cat);
                this.AddMetadataLocalPath(rt, cat, localPath);

                FileInfo info = new FileInfo(localPath);
                rt.Set(cat,MDKeys.Attributes,MDDescriptions.FileAttributes,info.Attributes.ToString());
                rt.Set(cat,MDKeys.CreationTime,MDDescriptions.FileCreationTime,this.ComputeDateTimeString(info.CreationTime));
                rt.Set(cat, MDKeys.LastAccessTime, MDDescriptions.FileLastAccessTime, this.ComputeDateTimeString(info.LastAccessTime));
                rt.Set(cat, MDKeys.LastWriteTime,MDDescriptions.FileLastWriteTime, this.ComputeDateTimeString(info.LastWriteTime));
                rt.Set(cat, MDKeys.Length, MDDescriptions.FileLength, this.ComputeSizeString(info.Length));
            }

            return rt;
        }




       






        private void AddMetadataLocalPath(Metadata rt, string category, string localPath)
        {
            rt.Set(category, MDKeys.LocalPath, MDDescriptions.LocalPath, localPath);
        }

        public static class MDCategories
        {
            public static readonly string File = "File";
            public static readonly string Folder = "Folder";
            public static readonly string LogicalDrive = "Logical Drive";
        }

        private static class MDKeys
        {
            public static readonly string LocalPath = "(LocalPath)";

            public static readonly string Length = "Length";
            public static readonly string LastWriteTime = "LastWriteTime";
            public static readonly string LastAccessTime = "LastAccessTime";
            public static readonly string CreationTime = "CreationTime";
            public static readonly string Attributes = "Attributes";

            public static readonly string AvailableFreeSpace = "AvailableFreeSpace";
            public static readonly string TotalFreeSpace = "TotalFreeSpace";
            public static readonly string TotalSize = "TotalSize";
            public static readonly string VolumeLabel = "VolumeLabel";
            public static readonly string DriveFormat = "DriveFormat";
            public static readonly string DriveType = "DriveType";
        }

        public static class MDDescriptions
        {
            public static readonly string LocalPath = "The underlying file-system-specific path.";

            public static readonly string FileLength = "The size, in bytes, of the current file.";
            public static readonly string FileLastWriteTime = "The time when the current file was last written to.";
            public static readonly string FileLastAccessTime = "The time when the current file was last accessed.";
            public static readonly string FileCreationTime = "The time when the current file was created.";
            public static readonly string FileAttributes = "The attributes of the current file.";

            public static readonly string FolderLastWriteTime = "The time when the current folder was last written to.";
            public static readonly string FolderLastAccessTime = "The time when the current folder was last accessed.";
            public static readonly string FolderCreationTime = "The time when the current folder was created.";
            public static readonly string FolderAttributes = "The attributes of the current folder.";

            public static readonly string DriveAvailableFreeSpace = "The amount of free space available on the drive.";
            public static readonly string DriveTotalFreeSpace = "The total free space available on the drive.";
            public static readonly string DriveTotalSize = "The total size of storage space on the drive.";
            public static readonly string DriveVolumeLabel = "The volume label of the drive.";
            public static readonly string DriveFormat = "The name of the file system, such as NTFS or FAT32.";
            public static readonly string DriveType = "The type of the drive.";
        }



        public ACL GetACL(Path path)
        {
            if (path.Level == 0)
            {
                return GetDefaultACL();
            }

            ACL rt = new ACL(true, GetDefaultACLPrincipal(), this.GetPermissionDescription);

            try
            {
                if (!path.IsFolder)
                {
                    System.IO.FileInfo fi = new System.IO.FileInfo(this.ToLocalPath(path));

                    this.AddPermissions(rt, fi.GetAccessControl());
                }
                else
                {
                    System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(this.ToLocalPath(path));

                    try
                    {
                        this.AddPermissions(rt, di.GetAccessControl());
                    }
                    catch (InvalidOperationException /*ex*/) { }

                }
            }
            catch (InvalidOperationException) { }

            return rt;

        }

        public void SetACL(Path path, ACL acl)
        {
            // TODO
        }

        public ReadOnlyList<ACLPrincipal> GetPrincipals()
        {
            // TODO
            return ReadOnlyList<ACLPrincipal>.Empty;
        }

        public ReadOnlyList<string> GetPermissions()
        {
            // TODO
            return ReadOnlyList<string>.Empty;
        }



























        private void AddPermissions(ACL acl, CommonObjectSecurity security)
        {
            foreach (AccessRule rule in security.GetAccessRules(true, true, typeof(SecurityIdentifier)))
            {
                SecurityIdentifier sid = rule.IdentityReference as SecurityIdentifier;

                bool isGroup = (sid.AccountDomainSid == null);

                string name = "(unknown)";

                try
                {
                    NTAccount account = sid.Translate(typeof(NTAccount)) as NTAccount;
                    name = account.Value;

                }
                catch (IdentityNotMappedException /*ex*/) { }

                ACLPrincipal p = new ACLPrincipal(sid.Value, name, isGroup);
               
                acl.Add(p, rule.AccessControlType.ToString());
            }

        }

        private string GetPermissionDescription(string permission)
        {
            NameValuePairs descriptions = new NameValuePairs();
            descriptions[AccessControlType.Allow.ToString()] = "The AccessRule object is used to allow access to a secured object.";
            descriptions[AccessControlType.Deny.ToString()] = "The AccessRule object is used to deny access to a secured object.";

            return descriptions.GetIfExists(permission);
        }

        private string ComputeDateTimeString(DateTime datetime)
        {
            if (datetime.Equals(DateTime.Parse("12/31/1600 7:00:00 PM")))
            {
                return string.Empty;
            }
            return datetime.ToString();
        }

        private static ACLPrincipal GetDefaultACLPrincipal()
        {
            ACLPrincipal rt = new ACLPrincipal("DEFAULT", "NAME", false);
            return rt;
        }

        private static ACL GetDefaultACL()
        {
            ACL rt = new ACL(true, GetDefaultACLPrincipal(), null);
            return rt;
        }








        private class LocalFolderItem : IFolderItem
        {

            private readonly IFileSystem _fileSystem;
            private readonly string _localPath;

            public LocalFolderItem(IFileSystem fileSystem, string localPath)
            {
                _fileSystem = fileSystem;
                if (!localPath.EndsWith(@"\"))
                {
                    localPath += @"\";
                }
                _localPath = localPath;

            }

            public Path Path
            {
                get { return Path.Parse(Path.SeparatorChar + _localPath.Replace('\\', Path.SeparatorChar)); }
            }

            public DateTime? LastModified
            {
                get { return new System.IO.DirectoryInfo(_localPath).LastWriteTime; }
            }

            public IFileSystem FileSystem
            {
                get { return _fileSystem; }
            }


        }

        private class FileItem : IFileItem, ISupportsQuickMD5
        {
            private readonly IFileSystem _fileSystem;
            private readonly string _localPath;

            public FileItem(IFileSystem fileSystem, string localPath)
            {
                _fileSystem = fileSystem;
                _localPath = localPath;
            }

            public IFileSystem FileSystem
            {
                get { return _fileSystem; }
            }

            public Path Path
            {
                get { return Path.Parse(Path.SeparatorChar + _localPath.Replace('\\', Path.SeparatorChar)); }
            }

            public DateTime? LastModified
            {
                get { return new System.IO.FileInfo(_localPath).LastWriteTime; }
            }

            public long Size
            {
                get { return new System.IO.FileInfo(_localPath).Length; }
            }

            public System.IO.Stream GetInputStream()
            {
                return System.IO.File.OpenRead(_localPath);
            }

            public byte[] QuickMD5
            {
                get
                {
                    using (Stream rt = this.GetInputStream())
                    {
                        return Utils.ToMD5(rt);
                    }
                }
            }

        }




     

       
    }
}
